#include <boost/format.hpp>
#include <boost/lexical_cast.hpp>

#include "Auxiliary.h"

using namespace boost::asio;
using namespace std;
using namespace tbb;


/// ServerInfo

ServerInfo::ServerInfo(const string &hostName, unsigned short port /* = 80 */)
	: _port(port)
{
	io_service _service;

	// Detecting true address of server.
	ip::tcp::resolver resolver(_service);
	ip::tcp::resolver::query query(hostName.c_str(), boost::lexical_cast<string>(port).c_str());
	ip::tcp::resolver::iterator it = resolver.resolve(query);
	ip::tcp::resolver::iterator end;

	boost::system::error_code error = boost::asio::error::host_not_found;

	while (error && it != end)
	{
		ip::tcp::socket s(_service);
		s.connect(*it, error);
		s.close();

		// Is it true endpoint?
		if (!error)
		{
			break;
		}

		it++;
	}

	if (error)
	{
		throw runtime_error((boost::format("Cannot connect to server: %1% (%2%)") % hostName % error.message()).str().c_str());
	}

	// Setting true endpoint.
	_address = *it;
	_ip = _address.address();
}

/// XmlLogger

XmlLogger::XmlLogger(const char *filename, double testTime)
{
	_totalTime = 0.0;
	_totalRequests = 0;
	_testTime = testTime;

	if ((_f = fopen(filename, "w")) == NULL)
	{
		throw runtime_error("Cannot create log file");
	}

	fprintf(_f, "<?xml version=\"1.0\"?>\n");
	fprintf(_f, "<Results>\n");
}

XmlLogger::~XmlLogger()
{
	double averageTime = _totalTime / (_totalRequests == 0 ? 1 : _totalRequests);

	fprintf(_f, "	<total>\n");
	fprintf(_f, "		<average_requests>%d</average_requests>\n", (size_t)(_totalRequests / _testTime));
	fprintf(_f, "		<average_time>%f</average_time>\n", averageTime);
	fprintf(_f, "	</total>\n");
	fprintf(_f, "</Results>\n");
	fclose(_f);
}

void XmlLogger::AddRecord(size_t writes, size_t connects, size_t startedRequests, size_t succeededRequests,
						  size_t failedRequests, size_t pingPongRequests, double elapsedTime)
{
	double averageTime = elapsedTime / (succeededRequests + failedRequests == 0 ? 1 : succeededRequests + failedRequests);
	_totalTime += elapsedTime;
	_totalRequests += succeededRequests + failedRequests;

	fprintf(_f, "	<record>\n");
	fprintf(_f, "		<started_requests>%d</started_requests>\n", startedRequests);
	fprintf(_f, "		<connects>%d</connects>\n", connects);
	fprintf(_f, "		<writes>%d</writes>\n", writes);
	fprintf(_f, "		<finished_requests>%d</finished_requests>\n", succeededRequests + failedRequests);
	fprintf(_f, "		<ping_pong>%d</ping_pong>\n", pingPongRequests);
	fprintf(_f, "		<average_time>%f</average_time>\n", averageTime);
	fprintf(_f, "	</record>\n");
}

/// Stats

Stats *Stats::_ref = new Stats();

Stats::Stats()
{
	_startedRequests = 0;
	_succeededRequests = 0;
	_failedRequests = 0;
	_pingPongRequests = 0;
	_elapsedTime = 0;
	_writes = 0;
	_connects = 0;
}

void Stats::RegisterRequest(bool IsSucceeded, tick_count::interval_t Duration)
{
	if (IsSucceeded)
	{
		_succeededRequests++;
	}
	else
	{
		_failedRequests++;
	}

	_elapsedTime.fetch_and_add((size_t)(Duration.seconds() * 1000000.0));
}

void Stats::GetInfo(size_t &writes, size_t &connects, size_t &StartedRequests, size_t &SucceededRequests, size_t &FailedRequests,
					size_t &PingPongRequests, double &TotalTime, bool ShouldClear /* = false */)
{
	if (ShouldClear)
	{
		// Interleaved vesrion of reading and clearing (for minimization of lost statistics).
		StartedRequests = _startedRequests;
		_startedRequests = 0;
		SucceededRequests = _succeededRequests;
		_succeededRequests = 0;
		FailedRequests = _failedRequests;
		_failedRequests = 0;
		PingPongRequests = _pingPongRequests;
		_pingPongRequests = 0;
		TotalTime = (double)_elapsedTime / 1000000.0;
		_elapsedTime = 0;

		writes = _writes;
		_writes = 0;

		connects = _connects;
		_connects = 0;
	}
	else
	{
		StartedRequests = _startedRequests;
		SucceededRequests = _succeededRequests;
		FailedRequests = _failedRequests;
		PingPongRequests = _pingPongRequests;
		TotalTime = (double)_elapsedTime / 1000000.0;

		writes = _writes;
		connects = _connects;
	}
}

void Stats::StartStatsPrinter(const char *filename, double time, bool shouldTerminate /* = false */)
{
	GetRef()->_startedThreads.push_back(new tbb_thread(Stats::entryPoint, filename,
													   std::pair<double, bool>(time, shouldTerminate)));
}

void Stats::entryPoint(const char *filename, pair<double, bool> params)
{
	try
	{
		// Printing statistics.
		XmlLogger logger(filename, params.first);
		size_t i = 0;

		while (i++ < params.first)
		{
			size_t started;
			size_t failed;
			size_t succeeded;
			size_t pingPong;
			size_t connected;
			size_t written;
			double time;
			Stats::GetRef()->GetInfo(written, connected, started, succeeded, failed, pingPong, time, true);
			logger.AddRecord(written, connected, started, succeeded, failed, pingPong, time);
			double average = time / (succeeded + failed == 0 ? 1 : succeeded + failed);
			printf("Started: %d, connected: %d, written: %d\n", started, connected, written);
			printf("Succeeded requests: %d, Failed requests: %d, Average time: %f\n\n",
				   succeeded, failed, average);
			this_tbb_thread::sleep(tick_count::interval_t(1.));
		}
	}
	catch (std::exception &ex)
	{
		printf("Error: %s\n", ex.what());
	}

	if (params.second)
	{
		exit(0);
	}
}
